
#include "Stdafx.h"
#include "VideoRenderer.h"
#include <dvdmedia.h>
#include <amvideo.h>
#include <memory>
#include "VideoPlayer.h"

using namespace SmallMediaLibrary;

int GetBytesPerPixel(enum PixelFormat format)
{
	switch(format)
	{
	case UByteRGB16:
		return 2;
	case UByteRGB24:
		return 3;
	case UByteRGB32:
	case UByteRGBA32:
		return 4;
	}

	return -1;
}

PixelFormat GetPixelFormatForSubtype(GUID guid)
{
	if(guid == MEDIASUBTYPE_RGB24)
	{
		return UByteRGB24;
	}	
	if(guid == MEDIASUBTYPE_RGB32)
	{
		return UByteRGB32;
	}
	if(guid == MEDIASUBTYPE_ARGB32)
	{
		return UByteRGBA32;
	}

	return Invalid;
}

VideoMemoryRenderer::VideoMemoryRenderer(LPUNKNOWN	pOwner, 
										HRESULT*	pResult) :
					 CBaseVideoRenderer(GUID_NULL,							// CLSID for this renderer
										L"Video-to-Memory Render Filter",	// Debug ONLY description
										pOwner,								// Aggregated owner object
										pResult)							// General OLE return code
{
	RECT r = {0,0,0,0};
	SourceSize = r;
	destination_region = r;
	SubPixelSampleCount = 1;
	InitializeCriticalSection(&receivingFrameMutex);
}

//all guids defined in uuids.h
//formats defined here: http://msdn.microsoft.com/en-us/library/windows/desktop/dd373477(v=vs.85).aspx (1/4 way down...)

//Compatible format types:
//MEDIASUBTYPE_ARGB32
//MEDIASUBTYPE_RGB32
//MEDIASUBTYPE_RGB24

//if we start encountering problems consider extending to support these aswell:
//MEDIASUBTYPE_RGB8
//MEDIASUBTYPE_RGB565
//MEDIASUBTYPE_RGB555


HRESULT VideoMemoryRenderer::CheckMediaType(const CMediaType *pmt)
{
	if(*(pmt->Type()) == MEDIATYPE_Video)
	{
		if(*(pmt->Subtype()) == MEDIASUBTYPE_RGB24)
		{
			return CheckMediaFormat(pmt);
		}
		
		if(*(pmt->Subtype()) == MEDIASUBTYPE_RGB32)
		{
			return CheckMediaFormat(pmt);
		}
		
		if(*(pmt->Subtype()) == MEDIASUBTYPE_ARGB32)
		{
			return CheckMediaFormat(pmt);
		}
	}

	return -1;
}

HRESULT VideoMemoryRenderer::CheckMediaFormat(const CMediaType *pmt)
{
	if(*(pmt->FormatType()) == FORMAT_VideoInfo)
	{
		VIDEOINFOHEADER info = *((VIDEOINFOHEADER*)(pmt->Format()));

		//Quick sanity check - if the compression is invalid we can't process it
		BITMAPINFOHEADER bitmapinfo = info.bmiHeader;
		if(!IsBitmapCompressionValid(bitmapinfo))
		{
			return -1;
		}

		return S_OK;
	}
		
	if(*(pmt->FormatType()) == FORMAT_VideoInfo2)
	{
		VIDEOINFOHEADER2 info = *((VIDEOINFOHEADER2*)(pmt->Format()));

		BITMAPINFOHEADER bitmapinfo = info.bmiHeader;
		if(!IsBitmapCompressionValid(bitmapinfo))
		{
			return -1;
		}

		return S_OK;
	}

	if(*(pmt->FormatType()) == FORMAT_MPEGVideo)
	{
		//we could support this but shouldn't have to (that is, we should always be able to find a decoder to output rgb bitmaps)
	}

	if(*(pmt->FormatType()) == FORMAT_DvInfo)
	{
		//we could support this but shouldn't have to
	}

	return -1;
}

bool VideoMemoryRenderer::IsBitmapCompressionValid(BITMAPINFOHEADER bmpinfo)
{
	if(bmpinfo.biCompression == BI_RGB)
		return true;

	//if(bmpinfo.biCompression == BI_BITFIELDS)	// - not supported
	
	return false;
}

HRESULT VideoMemoryRenderer::SetMediaType(const CMediaType *pmt)						
{
	if(*(pmt->Subtype()) == MEDIASUBTYPE_RGB24)
	{
		sourcePixelFormat = UByteRGB24;
	}	
	if(*(pmt->Subtype()) == MEDIASUBTYPE_RGB32)
	{
		sourcePixelFormat = UByteRGB32;
	}
	if(*(pmt->Subtype()) == MEDIASUBTYPE_ARGB32)
	{
		sourcePixelFormat = UByteRGBA32;
	}

	return LoadMediaFormat(pmt);
}

HRESULT VideoMemoryRenderer::LoadMediaFormat(const CMediaType *pmt)
{
	if(*(pmt->FormatType()) == FORMAT_VideoInfo)
	{
		VIDEOINFOHEADER info = *((VIDEOINFOHEADER*)(pmt->Format()));
		BITMAPINFOHEADER bitmapinfo = info.bmiHeader;

		//check that the bmi header and filter output agree on pixel format (if we have problems consider overriding sourcepixelformat with this...)
		if(GetBytesPerPixel(sourcePixelFormat) != (bitmapinfo.biBitCount / 8))
			return -1;

		// the width * bitcount aligned to the next boundary
		// http://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
		source_stride = ((((bitmapinfo.biWidth * bitmapinfo.biBitCount) + 31) & ~31) >> 3);

		// we havent seen a filter that acctually uses rcSource or rcTarget yet so we will let the application set the destination rect
		SourceSize.right = bitmapinfo.biWidth;
		SourceSize.bottom = bitmapinfo.biHeight;
		
		SourceRegion = info.rcSource;
		if((SourceRegion.right - SourceRegion.left) == 0)	//if rcSource is 0, set it to the frame size
			SourceRegion = SourceSize;

		destination_region = SourceRegion;

		return S_OK;
	}
		
	if(*(pmt->FormatType()) == FORMAT_VideoInfo2)
	{
		VIDEOINFOHEADER2 info = *((VIDEOINFOHEADER2*)(pmt->Format()));
		BITMAPINFOHEADER bitmapinfo = info.bmiHeader;

		if(GetBytesPerPixel(sourcePixelFormat) != (bitmapinfo.biBitCount / 8))
			return -1; 

		source_stride = ((((bitmapinfo.biWidth * bitmapinfo.biBitCount) + 31) & ~31) >> 3);

		SourceSize.right = bitmapinfo.biWidth;
		SourceSize.bottom = bitmapinfo.biHeight;

		SourceRegion = info.rcSource;
		if((SourceRegion.right - SourceRegion.left) == 0)
			SourceRegion = SourceSize;

		destination_region = SourceRegion;

		return S_OK;
	}

	return -1;
}


HRESULT VideoMemoryRenderer::DoRenderSample(IMediaSample *pMediaSample)
{
	EnterCriticalSection(&receivingFrameMutex);

	if(ReceivingFrame)
	{
		//timeout of 0, instead of holding up the decoder, we just drop frames if the user locks up the buffer
		if(ReceivingFrame->Lock->Wait(0))	
		{
			BYTE* source;
			HRESULT r = pMediaSample->GetPointer(&source);

			if(SUCCEEDED(r))
			{
				StretchBlt(
					(BYTE*)source,
					source_stride,
					SourceSize,
					SourceRegion,
					sourcePixelFormat,

					(BYTE*)((void*)(ReceivingFrame->Buffer)),
					ReceivingFrame->BufferStride,
					ReceivingFrame->BufferLength,
					ReceivingFrame->DestinationRegion->ToRect(),
					ReceivingFrame->PixelFormat
					);
			}

			ReceivingFrame->Lock->Release();
		}
	}

	LeaveCriticalSection(&receivingFrameMutex);

	if(Player)
		Player->OnFrameReady();

	return S_OK;
}

void VideoMemoryRenderer::StretchBlt(
	BYTE* source,
	int sourceStride,
	RECT sourceSize,
	RECT sourceRegion,
	PixelFormat srcFormat,

	BYTE* destination,
	int destinationStride,
	int destinationLength,
	RECT destRegion,
	PixelFormat destFormat
	)
{
	int destRegionWidth = (destRegion.right - destRegion.left);
	int destRegionHeight = (destRegion.bottom - destRegion.top);

	int sourceLines = (sourceSize.bottom - sourceSize.top);

	float dx = (float)(sourceRegion.right - sourceRegion.left) / (float)destRegionWidth;
	float dy = (float)(sourceRegion.bottom - sourceRegion.top) / (float)destRegionHeight;

	int destBytesPerPixel = GetBytesPerPixel(destFormat);

	for(int x = 0; x < destRegionWidth; x++)
	{
		for(int y = 0; y < destRegionHeight; y++)
		{
			int destX = (destRegion.left + x);
			int destY = (destRegion.top + y);

			float srcX = (x * dx) + sourceRegion.left;
			float srcY = (sourceLines - 1) - ((y * dy) + sourceRegion.top);

			Pixel p = SubSamplePixel(source, sourceStride, srcFormat, srcX, srcY, (sourceSize.right - sourceSize.left) - 1, sourceLines - 1, SubPixelSampleCount);

			int destOffset = ((destinationStride * destY) + (destX * destBytesPerPixel));
			*(Pixel*)(&destination[destOffset]) = p;
		}
	}
}

Pixel VideoMemoryRenderer::SamplePixel(
	BYTE* source, 
	int sourceStride, 
	PixelFormat sourceFormat, 
	int x, int y)
{
	int src1 = ((sourceStride * y) + (x * GetBytesPerPixel(sourceFormat)));
	Pixel p1 = *((Pixel*)(&source[src1]));
	return p1;
}

int Clamp(int value, int min, int max)
{
	if (value > max)
        value = max;
    else if (value < min)
        value = min;
	return value;
}

Pixel VideoMemoryRenderer::SubSamplePixel(
	BYTE* source, 
	int sourceStride, 
	PixelFormat sourceFormat, 
	float x, float y,
	int maxX, int maxY,
	int quality)
{
	FPixel p;

	x = (int)x;
	y = (int)y;
	
	p = p + SamplePixel(source, sourceStride, sourceFormat, x, y);
	int samplecount = 1;

	if(quality > 1)
	{
		p = p + SamplePixel(source, sourceStride, sourceFormat, Clamp(x + 1, 0, maxX) , y );
		p = p + SamplePixel(source, sourceStride, sourceFormat, x , Clamp(y + 1, 0, maxY) );
		samplecount = 3;
	}

	if(quality > 2)
	{
		p = p + SamplePixel(source, sourceStride, sourceFormat, Clamp(x - 1, 0, maxX) , y );
		p = p + SamplePixel(source, sourceStride, sourceFormat, x , Clamp(y - 1, 0, maxY) );
		samplecount = 5;
	}

	return p / samplecount;
}


VideoMemoryRenderer::~VideoMemoryRenderer()
{

}